﻿Imports Microsoft.Win32
Imports Microsoft.WindowsAzure, Microsoft.WindowsAzure.StorageClient
Imports System.Threading.Tasks
Imports System.IO

''' <summary>
''' Provides simplified, managed access to the Blob Storage on Windows Azure.
''' You can create/remove containers, upload/remove blobs
''' </summary>
''' <remarks></remarks>
Public Class BlobService

    Private blobStorage As CloudBlobClient
    Private AccountInfo As CloudStorageAccount

#Region "Events"
    ''' <summary>
    ''' Raised when a blob is starting to be uploaded
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event UploadBlobStarting(ByVal sender As Object, ByVal e As BlobServiceEventArgs)
    ''' <summary>
    ''' Raised when the blob has been successfully uploaded
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event UploadBlobCompleted(ByVal sender As Object, ByVal e As BlobServiceEventArgs)
    ''' <summary>
    ''' Raised when the delete operation on a blob is starting
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event DeleteBlobStarting(ByVal sender As Object, ByVal e As BlobServiceEventArgs)
    ''' <summary>
    ''' Raised when a blob has been successfully removed
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event DeleteBlobCompleted(ByVal sender As Object, ByVal e As BlobServiceEventArgs)

    ''' <summary>
    ''' Raised just before downloading a blob starts
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event DownloadBlobStarting(ByVal sender As Object, ByVal e As BlobServiceEventArgs)
    ''' <summary>
    ''' Raised when a blob download is completed
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event DownloadBlobCompleted(ByVal sender As Object, ByVal e As BlobServiceEventArgs)
#End Region

#Region "Shared helper methods"


    ''' <summary>
    ''' Generates the Endpoint name based on your account name
    ''' </summary>
    ''' <param name="AccountName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CreateBlobEndPointFromAccountName(ByVal AccountName As String) As String
        If String.IsNullOrEmpty(AccountName) = True Then
            Return String.Empty
        Else
            Dim name As String = String.Concat("http://", AccountName, ".blob.core.windows.net")

            Return name
        End If
    End Function


    ''' <summary>
    ''' Returns the MIME type for the specified extension
    ''' </summary>
    ''' <param name="fileName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function getMimeType(ByVal fileName As String) As String

        'in case the extension is not registered, then return a default value
        Dim mimeType As String = "application/unknown"

        Dim fileExtension = System.IO.Path.GetExtension(fileName).ToLower()
        Dim registryKey = Registry.ClassesRoot.OpenSubKey(fileExtension)

        If registryKey IsNot Nothing And registryKey.GetValue("Content Type") IsNot Nothing Then

            mimeType = registryKey.GetValue("Content Type").ToString
        End If

        Return mimeType

    End Function

    ''' <summary>
    ''' Checks if the Storage Emulator is running. If not, starts it
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function CheckIfDevelopmentStorageIsRunning() As Boolean
        'If the development storage is not running, then launches it
        Dim IsDevelopmentStorageRunning = Process.GetProcessesByName("DSService")

        Dim sdkPath As String = CStr(My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\ServiceHosting\v1.3", "InstallPath", Nothing))

        If IsDevelopmentStorageRunning.Length = 0 Then

            Try
                Process.Start(sdkPath + "bin\devstore\DSService.exe")
                System.Threading.Thread.Sleep(4500)

                IsDevelopmentStorageRunning = Nothing
                Return True
            Catch ex As Exception
                Return False
            End Try
            'Waiting for the Development Storage to be ready.
        Else
            Return True
        End If
    End Function
#End Region

#Region "Container methods"
    ''' <summary>
    ''' Retrieves a list of all the containers in the Blob storage
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Function ListContainers() As IEnumerable(Of CloudBlobContainer)
        Try
            Dim result = blobStorage.ListContainers
            Return result

            'This is thrown if an invalid account is passed (e.g. the user clicks Go but fields are empty)
        Catch ex As UriFormatException
            Throw New UriFormatException
        Catch ex As Exception
            Throw New Exception(ex.InnerException.Message)
        End Try
    End Function
    ''' <summary>
    ''' Creates a new Container in the Blob storage with the given name and accessibility setting
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <param name="IsPublic"></param>
    ''' <remarks></remarks>
    Public Overridable Sub CreateContainer(ByVal ContainerName As String, ByVal IsPublic As Boolean)

        Try
            Dim container = blobStorage.GetContainerReference(ContainerName)
            container.CreateIfNotExist()

            If IsPublic = True Then
                container.SetPermissions(New BlobContainerPermissions With {.PublicAccess = BlobContainerPublicAccessType.Container})
            Else
                container.SetPermissions(New BlobContainerPermissions With {.PublicAccess = BlobContainerPublicAccessType.Off})

            End If

        Catch ex As StorageServerException
            Throw New StorageServerException
        Catch ex As Exception
            Throw New Exception(ex.InnerException.Message)
        End Try

    End Sub

    ''' <summary>
    ''' Removes the specified container from the Blob storage
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <remarks></remarks>
    Public Sub DeleteContainer(ByVal ContainerName As String)
        Dim container = blobStorage.GetContainerReference(ContainerName)
        container.BeginDelete(Sub(result As IAsyncResult)
                                  container.EndDelete(result)
                              End Sub, container)
    End Sub
#End Region

#Region "Blob methods"

    ''' <summary>
    ''' Retrieves a list of all the Blobs for the given Container
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Function ListBlobs(ByVal ContainerName As String) As IEnumerable(Of CloudBlob)
        Try
            Dim container = blobStorage.GetContainerReference(ContainerName)
            Dim temp = container.ListBlobs
            Dim lst As New List(Of CloudBlob)

            For Each item In temp
                lst.Add(CType(item, CloudBlob))
            Next
            Return lst
        Catch ex As Exception
            Return Nothing
        End Try

    End Function

    ''' <summary>
    ''' Removes the specified blob from the specified container on storage
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <param name="BlobName"></param>
    ''' <remarks></remarks>
    Public Sub DeleteBlob(ByVal ContainerName As String, ByVal BlobName As String)
        Try
            Dim container = blobStorage.GetContainerReference(ContainerName)

            Dim blob = container.GetBlobReference(BlobName)
            RaiseEvent DeleteBlobStarting(Me, New BlobServiceEventArgs(blob))
            blob.BeginDeleteIfExists(Sub(result As IAsyncResult)
                                         blob.EndDeleteIfExists(result)
                                         RaiseEvent DeleteBlobCompleted(Me, New BlobServiceEventArgs(blob))
                                     End Sub, blob)
        Catch ex As Exception
            Throw
        End Try

    End Sub


    ''' <summary>
    ''' Removes the specified blob from the specified container on storage
    ''' </summary>
    ''' <param name="BlobName"></param>
    ''' <remarks></remarks>
    Public Sub DeleteBlob(ByVal BlobName As Uri)
        Dim blob = Me.blobStorage.GetBlobReference(BlobName.ToString)
        RaiseEvent DeleteBlobStarting(Me, New BlobServiceEventArgs(blob))
        blob.BeginDeleteIfExists(Sub(result As IAsyncResult)
                                     blob.EndDeleteIfExists(result)
                                     RaiseEvent DeleteBlobCompleted(Me, New BlobServiceEventArgs(blob))
                                 End Sub, blob)
    End Sub


    ''' <summary>
    ''' Removes one or more blobs from the specified container on storage
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub DeleteBlobs(ByVal Blobs As IEnumerable(Of CloudBlob))

        For Each blob In Blobs
            Dim blobRef = Me.blobStorage.GetBlobReference(blob.Uri.ToString)
            blobRef.BeginDeleteIfExists(Sub(result As IAsyncResult)
                                            RaiseEvent DeleteBlobCompleted(Me, New BlobServiceEventArgs(blobRef))
                                            blobRef.EndDeleteIfExists(result)
                                        End Sub, blobRef.Uri)
        Next

    End Sub

    ''' <summary>
    ''' Creates a new Blob on the storage uploading the specified file to the specified container
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <param name="PathName"></param>
    ''' <remarks>This method works asynchronously</remarks>
    Public Sub UploadBlob(ByVal ContainerName As String, ByVal PathName As String)

        Dim container = blobStorage.GetContainerReference(ContainerName)


        Dim blobName = System.IO.Path.GetFileName(PathName)

        Try
            Dim blobRef = container.GetBlobReference(blobName)
            blobRef.Properties.ContentType = getMimeType(PathName)
            blobRef.Properties.ContentLanguage = My.Computer.Info.InstalledUICulture.EnglishName
            RaiseEvent UploadBlobStarting(Me, New BlobServiceEventArgs(blobRef))
            Using fs As New IO.FileStream(PathName, IO.FileMode.Open)
                blobRef.BeginUploadFromStream(fs, AddressOf UploadAsyncCallBack, blobRef)
            End Using

        Catch ex As IO.IOException
            Throw New IO.IOException("File " + IO.Path.GetFileName(PathName) + " is locked by another process and won't be uploaded")
        Catch ex As Exception
            Throw New Exception(ex.ToString)
        End Try
    End Sub

    ''' <summary>
    ''' Uploads multiple files to the specified container in the storage
    ''' </summary>
    ''' <param name="ContainerName"></param>
    ''' <param name="BlobList"></param>
    ''' <remarks></remarks>
    Public Sub UploadBlobs(ByVal ContainerName As String, ByVal BlobList As String())
        Try
            Dim container = blobStorage.GetContainerReference(ContainerName)

            For Each blob In BlobList
                Dim blobRef = container.GetBlobReference(IO.Path.GetFileName(blob))
                blobRef.Properties.ContentType = getMimeType(blob)
                blobRef.Properties.ContentLanguage = My.Computer.Info.InstalledUICulture.EnglishName
                RaiseEvent UploadBlobStarting(Me, New BlobServiceEventArgs(blobRef))
                Dim fs As New FileStream(blob, FileMode.Open)
                blobRef.BeginUploadFromStream(fs, AddressOf UploadAsyncCallBack, blobRef)
            Next
        Catch ex As Exception
            Throw
        End Try

    End Sub

    ''' <summary>
    ''' Download one or more blobs to a local path. Required against private containers
    ''' </summary>
    ''' <param name="containerName"></param>
    ''' <param name="targetDirectory"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overridable Function DownloadBlobs(ByVal containerName As String, ByVal targetDirectory As String) As IEnumerable(Of String)

        Try
            Dim container = blobStorage.GetContainerReference(containerName)

            Dim blobNames As New List(Of String)
            Dim tempStream As IO.FileStream

            For Each blob In container.ListBlobs.ToCloudBlobCollection
                Dim tempPath = targetDirectory + "\" + IO.Path.GetFileName(blob.Uri.AbsolutePath)
                tempStream = New IO.FileStream(tempPath, IO.FileMode.Create)
                blobNames.Add(tempPath)
                RaiseEvent DownloadBlobStarting(Me, New BlobServiceEventArgs(blob))

                blob.BeginDownloadToStream(tempStream, AddressOf DownloadAsyncCallBack, New KeyValuePair(Of CloudBlob, FileStream)(blob, tempStream))

            Next
            Return blobNames.AsEnumerable
        Catch ex As Exception
            Throw
        End Try
    End Function

    Public Overridable Sub DownloadBlobs(ByVal containerName As String, ByVal targetDirectory As String, ByVal blobsToDownload As IEnumerable(Of CloudBlob))
        Dim container = blobStorage.GetContainerReference(containerName)

        Dim tempStream As IO.FileStream

        For Each blob In blobsToDownload
            Dim tempPath = targetDirectory + "\" + IO.Path.GetFileName(blob.Uri.AbsolutePath)
            tempStream = New IO.FileStream(tempPath, IO.FileMode.Create)
            RaiseEvent DownloadBlobStarting(Me, New BlobServiceEventArgs(blob))

            blob.BeginDownloadToStream(tempStream, AddressOf DownloadAsyncCallBack, New KeyValuePair(Of CloudBlob, FileStream)(blob, tempStream))

        Next

    End Sub
#End Region



    Public Sub New(ByVal accountName As String, ByVal sharedKey As String)
        'Dim cs As New StorageCredentialsAccountAndKey(accountName, sharedKey)
        'Me.AccountInfo = New CloudStorageAccount(cs, True)
        Me.AccountInfo = CloudStorageAccount.Parse("DefaultEndpointsProtocol=http;AccountName=" + accountName + ";AccountKey=" + sharedKey)
        blobStorage = AccountInfo.CreateCloudBlobClient()
    End Sub

    Public Sub New()
        'Use the dev account
        Me.AccountInfo = CloudStorageAccount.DevelopmentStorageAccount
        blobStorage = AccountInfo.CreateCloudBlobClient()

    End Sub

    Public ReadOnly Property BlobEndPoint As Uri
        Get
            Return Me.AccountInfo.BlobEndpoint
        End Get
    End Property

#Region "Callbacks"
    Private Sub UploadAsyncCallBack(ByVal result As IAsyncResult)
        If result.IsCompleted Then
            Dim blobref = CType(result.AsyncState, CloudBlob)
            blobref.SetProperties()
            blobref.EndUploadFromStream(result)
            RaiseEvent UploadBlobCompleted(Me, New BlobServiceEventArgs(blobRef))
        End If
    End Sub

    Private Sub DownloadAsyncCallBack(ByVal result As IAsyncResult)
        Dim currentResult = CType(result.AsyncState, KeyValuePair(Of CloudBlob, FileStream))
        Dim blob = currentResult.Key
        blob.EndDownloadToStream(result)
        currentResult.Value.Close()
        RaiseEvent DownloadBlobCompleted(Me, New BlobServiceEventArgs(blob))
    End Sub
#End Region



End Class
